/*
 *  Copyright 2009-2016 Weibo, Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.weibo.api.motan.rpc;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 
 * rpc call statistic.
 *
 * @author fishermen
 * @version V1.0 created at: 2013-5-23
 */

public class RpcStats {

	private static final String SEPERATOR_METHOD_AND_PARAM = "|";

	private static ConcurrentHashMap<String, StatInfo> serviceStat = new ConcurrentHashMap<String, RpcStats.StatInfo>();
	private static ConcurrentHashMap<String, ConcurrentHashMap<String, StatInfo>> methodStat = new ConcurrentHashMap<String, ConcurrentHashMap<String, StatInfo>>();

//	private static ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1);
//	static {
//		ShutDownHook.registerShutdownHook(new Closable() {
//			@Override
//			public void close() {
//				if (!scheduledExecutor.isShutdown()) {
//					scheduledExecutor.shutdown();
//				}
//			}
//		});
//	}

	/**
	 * call before invoke the request
	 * 
	 * @param url
	 * @param request
	 */
	public static void beforeCall(URL url, Request request) {
		String uri = url.getUri();
		onBeforeCall(getServiceStat(uri));
		onBeforeCall(getMethodStat(uri, request.getMethodName(), request.getParamtersDesc()));
	}

	/**
	 * call after invoke the request
	 * 
	 * @param url
	 * @param request
	 * @param success
	 * @param procTimeMills
	 */
	public static void afterCall(URL url, Request request, boolean success, long procTimeMills) {
		String uri = url.getUri();
		onAfterCall(getServiceStat(uri), success, procTimeMills);
		onAfterCall(getMethodStat(uri, request.getMethodName(), request.getParamtersDesc()), success, procTimeMills);
	}

	public static StatInfo getServiceStat(URL url) {
		return getServiceStat(url.getUri());
	}

	public static StatInfo getMethodStat(URL url, Request request) {
		return getMethodStat(url.getUri(), request.getMethodName(), request.getParamtersDesc());
	}

	private static StatInfo getServiceStat(String uri) {
		StatInfo stat = serviceStat.get(uri);
		if (stat == null) {
			stat = new StatInfo();
			serviceStat.putIfAbsent(uri, stat);
			stat = serviceStat.get(uri);
		}
		return stat;
	}

	private static StatInfo getMethodStat(String uri, String methodName, String methodParaDesc) {
		ConcurrentHashMap<String, StatInfo> sstats = methodStat.get(uri);
		if (sstats == null) {
			sstats = new ConcurrentHashMap<String, StatInfo>();
			methodStat.putIfAbsent(uri, sstats);
			sstats = methodStat.get(uri);
		}

		String methodNameAndParams = methodName + SEPERATOR_METHOD_AND_PARAM + methodParaDesc;
		StatInfo mstat = sstats.get(methodNameAndParams);
		if (mstat == null) {
			mstat = new StatInfo();
			sstats.putIfAbsent(methodNameAndParams, mstat);
			mstat = sstats.get(methodNameAndParams);
		}
		return mstat;
	}

	private static void onBeforeCall(StatInfo statInfo) {
		statInfo.activeCount.incrementAndGet();
	}

	private static void onAfterCall(StatInfo statInfo, boolean success, long procTimeMills) {
		statInfo.activeCount.decrementAndGet();
		if (!success) {
			statInfo.failCount.incrementAndGet();
		}
		statInfo.totalCountTime.inc(1, procTimeMills);
		statInfo.latestCountTime.inc(1, procTimeMills);
	}

	@SuppressWarnings("unused")
	private static void startCleaner() {

	}

	@SuppressWarnings("unused")
	private static void cleanLatestStat() {
		if (serviceStat.size() == 0) {
			return;
		}
		for (StatInfo si : serviceStat.values()) {
			si.resetLatestStat();
		}

	}

	public static class StatInfo {

		private AtomicInteger activeCount = new AtomicInteger();
		private AtomicLong failCount = new AtomicLong();
		private CountTime totalCountTime = new CountTime();
		private CountTime latestCountTime = new CountTime();

		public int getActiveCount() {
			return activeCount.get();
		}

		public long getFailCount() {
			return failCount.get();
		}

		public CountTime getTotalCountTime() {
			return totalCountTime;
		}

		public CountTime getLatestCountTime() {
			return latestCountTime;
		}

		public void resetLatestStat() {
			latestCountTime.reset();
		}
	}

	public static class CountTime {
		private AtomicLong count;
		private AtomicLong timeMills;

		public CountTime() {
			count = new AtomicLong();
			timeMills = new AtomicLong();
		}

		private void inc(int incCount, long incTimeMills) {
			count.getAndAdd(incCount);
			timeMills.getAndAdd(incTimeMills);
		}

		public long getCount() {
			return count.get();
		}

		public void reset() {
			count.set(0);
			timeMills.set(0);
		}

	}

}
